/************************************************************************
                                                                     
   Xceed Ultimate ListBox for Silverlight                                                                                                                                            
   Copyright (C) 2010 Xceed Software Inc.    
                                                                     
   This program is provided to you under the terms of the GNU General Public  
   License version 2 as published by the Free Software Foundation. 
        
   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY, without even the implied warranty of 
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License along 
   with this program, if not, write to the Free Software Foundation, Inc., 
   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

   This program can be provided to you by Xceed Software Inc. under a
   proprietary commercial license agreement for use in non-Open Source
   projects. Visit Xceed.com to find the commercial edition of 
   Xceed Ultimate ListBox for Silverlight.                                    
                                                                      
 **********************************************************************/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;

namespace Xceed.Silverlight.Data
{
  internal sealed class GroupHelper : GroupHelperBase
  {
    public GroupHelper( IDataHelper childHelper, GroupHandler groupHandler )
      : base( childHelper )
    {
      if( groupHandler == null )
        throw new ArgumentNullException( "groupHandler" );

      m_groupHandler = groupHandler;
    }


    #region GroupHandler Property

    public override GroupHandler GroupHandler
    {
      get
      {
        return m_groupHandler;
      }
    }

    private GroupHandler m_groupHandler; //null

    #endregion GroupHandler Property


    public override object GetMemberValue( string memberName, object item )
    {
      object value;

      if( !this.TryGetMemberValue( memberName, item, out value ) )
        throw new ArgumentException( "The specified member was not found on the item.", "memberName" );

      return value;
    }

    public override bool TryGetMemberValue( string memberName, object item, out object value )
    {
      object groupValue = null;
      bool success = false;
      bool isGroupItem = false;
      DataPath dataPath = item as DataPath;

      if( dataPath != null )
      {
        isGroupItem = this.IsGroupRelatedPath( dataPath );

        int indexOfMember = m_groupHandler.PropertyNames.ToList().IndexOf( memberName );

        if( indexOfMember >= 0 && dataPath.Depth > indexOfMember )
        {
          GroupDataItem groupDataItem = dataPath[ indexOfMember ] as GroupDataItem;

          if( groupDataItem != null )
          {
            success = true;
            groupValue = groupDataItem.Data;
          }
        }
      }

      if( success )
      {
        value = groupValue;
        return true;
      }
      else if( isGroupItem )
      {
        value = null;
        return false;
      }
      else
      {
        return base.TryGetMemberValue( memberName, item, out value );
      }
    }

    public override int Compare( object firstItem, object secondItem )
    {
      if( firstItem == null )
        throw new ArgumentNullException( "firstItem" );

      if( secondItem == null )
        throw new ArgumentNullException( "secondItem" );

      Nullable<int> sourcePositionCompare =
        ParentHelper<DataPath>.HandleSourcePositions( ref firstItem, ref secondItem );

      if( sourcePositionCompare.HasValue )
      {
        return sourcePositionCompare.Value;
      }

      object firstComparisonObject = this.ConvertBackLocal( firstItem );
      object secondComparisonObject = this.ConvertBackLocal( secondItem );

      bool firstIsGroup = ( firstComparisonObject == null ) && ( ( DataPath )firstItem ).LastChild is GroupDataItem;
      bool secondIsGroup = ( secondComparisonObject == null ) && ( ( DataPath )secondItem ).LastChild is GroupDataItem;

      bool firstIsGroupHeader = ( firstComparisonObject == null ) && ( ( DataPath )firstItem ).LastChild is HeaderFooterDataItem;
      bool secondIsGroupHeader = ( secondComparisonObject == null ) && ( ( DataPath )secondItem ).LastChild is HeaderFooterDataItem;

      System.Diagnostics.Debug.Assert( firstComparisonObject != null || firstIsGroup || firstIsGroupHeader );
      System.Diagnostics.Debug.Assert( secondComparisonObject != null || secondIsGroup || secondIsGroupHeader );

      Func<DataPath, object, bool, bool, object> handleGroupItemWithOtherItem =
      ( itemPath, otherItem, itemIsGroup, itemIsHeader ) =>
      {
        if( itemIsGroup )
        {
          //Comparaison with a child item is not valid
          if( otherItem is DataPath )
          {
            DataPath otherPath = ( DataPath )otherItem;
            if( itemPath.IsAncestorOf( otherPath ) )
              throw new InvalidOperationException( "Cannot compare a path with one of its descendants." );
          }

          return this.CreateSortPositionForGroup( itemPath, StartPosition.Before );
        }
        else if( itemIsHeader )
        {
          HeaderFooterDataItem itemHeader = itemPath.LastChild as HeaderFooterDataItem;
          StartPosition startPosition = ( itemHeader.IsHeader )
            ? StartPosition.Before
            : StartPosition.After;

          return this.CreateSortPositionForGroup( itemPath.CreateAncestorPath( itemPath.Depth - 1 ), startPosition );
        }

        throw new DataInternalException();
      };

      if( firstIsGroup || firstIsGroupHeader )
      {
        firstComparisonObject = handleGroupItemWithOtherItem( ( DataPath )firstItem, secondItem, firstIsGroup, firstIsGroupHeader );
      }

      if( secondIsGroup || secondIsGroupHeader )
      {
        secondComparisonObject = handleGroupItemWithOtherItem( ( DataPath )secondItem, firstItem, secondIsGroup, secondIsGroupHeader );
      }

      int compareResult = this.ChildHelper.Compare( firstComparisonObject, secondComparisonObject );

      if( compareResult == 0 )
      {
        if( firstIsGroup && secondIsGroupHeader )
          Debug.Assert( false );

        if( secondIsGroup && firstIsGroupHeader )
          Debug.Assert( false );

        //Equality is not true if comparaison was made between a group and a "real" SortPosition...
        if( firstIsGroup && !secondIsGroup )
        {
          compareResult = 1;
        }
        else if( !firstIsGroup && secondIsGroup )
        {
          compareResult = -1;
        }
      }

      return compareResult;
    }

    public override void InvalidateItem( DataPath dataPath )
    {
      //Only the data path that targets a user object can be invalidated.
      if( dataPath != null && !this.IsGroupRelatedPath( dataPath ) )
      {
        base.InvalidateItem( dataPath );
      }
    }

    internal override object ConvertLocal( object value )
    {
      if( value == null )
        throw new ArgumentNullException( "value" );

      DataPath currentItem = ( DataPath )value;

      if( this.GroupHandler.GroupDescriptions.Count == 0 )
        return currentItem;

      string[] propertyNames = m_groupHandler.PropertyNames.ToArray();
      DataItem[] groupStack = new DataItem[ propertyNames.Length + currentItem.Depth ];
      for( int i = 0; i < propertyNames.Length; i++ )
      {
        string propertyName = propertyNames[ i ];
        object groupValue;

        groupValue = this.ChildHelper.GetMemberValue( propertyName, currentItem );
        groupStack[ i ] = new GroupDataItem( groupValue );
      }

      currentItem.Path.CopyTo( groupStack, propertyNames.Length );

      return new DataPath( groupStack, true );
    }

    internal override object ConvertBackLocal( object value )
    {
      if( value == null )
        throw new ArgumentNullException( "value" );

      if( !( value is DataPath ) )
        return value;

      DataPath path = ( DataPath )value;

      if( path.LastChild is GroupDataItem || path.LastChild is HeaderFooterDataItem )
        return null;

      int groupCount = m_groupHandler.GroupDescriptions.Count;

      if( path.Depth < groupCount + 1 )
        throw new InvalidOperationException( "The specified data path is invalid." );

      DataItem[] pathNoGrouping = new DataItem[ path.Depth - groupCount ];
      Array.Copy( path.Path, groupCount, pathNoGrouping, 0, pathNoGrouping.Length );

      return new DataPath( pathNoGrouping );
    }

    internal SortPosition CreateSortPositionForGroup( DataPath groupPath, StartPosition startPosition )
    {
      if( groupPath == null )
        throw new ArgumentNullException( "groupPath" );

      if( !( groupPath.LastChild is GroupDataItem ) )
        throw new InvalidOperationException( "The groupPath does not refer to a group." );

      if( groupPath.Depth > m_groupHandler.GroupDescriptions.Count )
        throw new DataInternalException();//The specified groupPath does not correspond to the current grouping.

      IList<object> groupsValues = groupPath.Path.Select( ( dataItem ) => dataItem.Data ).ToList();

      return new SortPosition( this.CompleteSort.ToList(), groupsValues, startPosition );
    }

    internal void CreatePathFromSortPosition( SortPosition sortPosition, out DataPath path, out Nullable<StartPosition> startPosition )
    {
      int sortValueCount = sortPosition.SortValues.Count;
      int pathDepth = Math.Min( sortValueCount, this.GroupHandler.GroupDescriptions.Count );
      DataItem[] pathItems = new DataItem[ pathDepth ];
      for( int i = 0; i < pathDepth; i++ )
      {
        pathItems[ i ] = new GroupDataItem( sortPosition.SortValues[ i ] );
      }

      startPosition = ( pathDepth == sortValueCount )
        ? sortPosition.StartPosition
        : ( Nullable<StartPosition> )null;

      path = new DataPath( pathItems );
    }
  }
}
